﻿using System;
using System.Security.Cryptography;
using System.Text;

namespace Signit.Sdk.Util
{
    /// <summary>
    /// <p>用于对称加密算法的简化使用扩展方法工具类。该工具类使用户只需要提供密码即可对数据进行加解密。
    /// 加解密所使用的初始化向量是通过密码来计算的。</p>
    /// <p>由于该工具类使用了特定的生成初始化向量的算法，所以 <c>SymmetricTool.Decrypt</c> 方法只能解密</p>
    /// 由 <c>SymmetricTool.Encrypt</c> 生成的，或通过其兼容算法生成的密文。
    /// </summary>
    /// <example><code>
    /// string EncryptConnectionString(string connStr, string password) {
    ///     Aes aes = Aes.Create();
    ///     byte[] ed = aes.Encrypt(password, connStr.ToByteArray(Encoding.UTF8));
    ///     return ed.HexEncode();
    /// }
    /// 
    /// string DecryptConnectionString(string hexStr, string password) {
    ///     byte[] d = Aes.Create().Decrypt(password, hexStr.HexDecode());
    ///     return d.GetString(Encoding.UTF8);
    /// }
    /// </code></example>
    /// <remark>
    /// 对于 AES、DES 和 3DES 算法，可以直接使用 <c>AesTool</c> 或 <c>DesTool</c> 中定义的相关扩展方法。
    /// </remark>
    /// <see cref="AesTool" />
    /// <see cref="DesTool" />
    public static class SymmetricTool
    {
        /// <summary>
        /// 根据指定的对称加密算法，使用给定的密码对数据进行加密。
        /// </summary>
        /// <param name="me">对称加密算法</param>
        /// <param name="password">密码，不能为 <c>null</c>，可以是 <c>string.Empty</c></param>
        /// <param name="data">需要加密的数据，数据为 <c>null</c>，则加密结果为 <c>null</c></param>
        /// <returns>已加密的数据</returns>
        /// <exception cref="ArgumentNullException"><c>password</c> 为 <c>null</c></exception>
        /// <remark>
        /// 密码原文会通过 UTF-8 编码方式转换成字符数据用于加密。
        /// </remark>
        public static byte[] Encrypt(this SymmetricAlgorithm me, string password, byte[] data)
        {
            if (password == null)
            {
                throw new ArgumentNullException("password");
            }

            if (data == null)
            {
                return null;
            }

            byte[] keyData = Encoding.UTF8.GetBytes(password);
            byte[] key = InternalTool.PadKey(keyData, me.KeySize / 8);
            byte[] iv = InternalTool.PadKey(keyData, me.BlockSize / 8);
            return InternalTool.Encrypt(me, data, iv, key);
        }

        /// <summary>
        /// 根据指定的对称加密算法，使用给定的密码对数据的一部分或全部进行加密。
        /// </summary>
        /// <param name="me">对称加密算法</param>
        /// <param name="password">密码，不能为 <c>null</c>，可以是 <c>string.Empty</c></param>
        /// <param name="data">需要加密的数据，数据不能为 <c>null</c></param>
        /// <param name="start">原数据起始位置</param>
        /// <param name="length">需要加密的原数据长度</param>
        /// <returns>已加密的数据</returns>
        /// <exception cref="ArgumentNullException"><c>password</c> 或 <c>data</c> 为 <c>null</c></exception>
        /// <remark>
        /// 密码原文会通过 UTF-8 编码方式转换成字符数据用于加密。
        /// </remark>
        public static byte[] Encrypt(this SymmetricAlgorithm me, string password, byte[] data, int start, int length)
        {
            if (password == null)
            {
                throw new ArgumentNullException("password");
            }

            if (data == null)
            {
                throw new ArgumentNullException("data");
            }

            byte[] keyData = Encoding.UTF8.GetBytes(password);
            byte[] key = InternalTool.PadKey(keyData, me.KeySize / 8);
            byte[] iv = InternalTool.PadKey(keyData, me.BlockSize / 8);
            return InternalTool.Encrypt(me, data, start, length, iv, key);
        }

        /// <summary>
        /// 根据指定的对称加密算法，使用给定的密码对加密数据进行解密。
        /// </summary>
        /// <param name="me">对称加密算法</param>
        /// <param name="password">密码，不能为 <c>null</c>，可以是 <c>string.Empty</c></param>
        /// <param name="encryptedData">已加密的数据</param>
        /// <returns>解密后的数据</returns>
        /// <exception cref="ArgumentNullException"><c>password</c> 为 <c>null</c></exception>
        /// <remark>
        /// 密码原文会通过 UTF-8 编码方式转换成字符数据用于解密。
        /// </remark>
        public static byte[] Decrypt(this SymmetricAlgorithm me, string password, byte[] encryptedData)
        {
            if (password == null)
            {
                throw new ArgumentNullException("password");
            }

            if (encryptedData == null)
            {
                return null;
            }

            byte[] keyData = Encoding.UTF8.GetBytes(password);
            byte[] key = InternalTool.PadKey(keyData, me.KeySize / 8);
            byte[] iv = InternalTool.PadKey(keyData, me.BlockSize / 8);
            return InternalTool.Decrypt(me, encryptedData, iv, key);
        }
    }
}
